package com.mgq.juc.design.flyweight;

import lombok.extern.slf4j.Slf4j;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicIntegerArray;

import static java.lang.Thread.sleep;

/**
 * @author MaGuangQi
 * 自定义连接池实现. 使用了CAS, wait/notify 原子数组 AtomicIntegerArray
 * @date 2021-12-20 09:44
 **/
public class TestConnectCache {
    public static void main(String[] args) {
        Pool pool = new Pool(2);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                Connection connection = pool.borrow();
                try {
                    sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    pool.free(connection);
                }
            }).start();
        }
    }
}

@Slf4j
class Pool {
    //连接池大小
    private int poolSize;
    //连接对象,用来保存连接(这个对象不用线程安全,因为初始化就会创建好,其他线程不会更改这个数组,只会更改连接状态数组)
    private Connection[] connections;
    //连接状态数组 0 表示 空闲, 1表示繁忙.和Connection[]对应
    private AtomicIntegerArray states;

    public Pool(int poolSize) {
        this.poolSize = poolSize;
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(poolSize);
        for (int i = 0; i < poolSize; i++) {
            Connection connection = new MockConnection("连接" + (i + 1));
            connections[i] = connection;
            // states.set(i, 0); int 默认就是0
        }
    }

    /**
     * 借连接
     *
     * @return
     */
    public Connection borrow() {
        while (true) {
            for (int i = 0; i < poolSize; i++) {
                //表示空闲
                if (states.get(i) == 0) {
                    //此时需要使用CAS来保证更改成功,成功才能返回
                    if (states.compareAndSet(i, 0, 1)) {
                        log.debug("借到连接,{}", connections[i]);
                        return connections[i];
                    }
                }
            }
            // 必须等待,否则会导致CAS一直空转
            //执行到这,就说明池内的连接都没有借到,表示连接池已经满了
            //需要考虑连接池满了,没有借到连接,此时就应该等待
            synchronized (this) {
                try {
                    log.debug("等待.......");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 归还连接
     *
     * @param connection
     */
    public void free(Connection connection) {
        //检查归还的连接是否是连接池内的
        for (int i = 0; i < poolSize; i++) {
            if (connections[i] == (connection)) {
                //归还连接 (此时不需要CAS,只要它获得了连接,这个连接,就归它使用,归还也没有线程竞争这个值,所以不需要CAS)
                states.set(i, 0);
                log.debug("释放连接,{}", connections[i]);
                synchronized (this) {
                    //通知等待的线程
                    this.notifyAll();
                }
                break;
            }
        }
    }
}

@Slf4j
/**
 * 使用Semaphore优化(好处是代码易读)
 */
class PoolV2 {
    //连接池大小
    private int poolSize;
    //连接对象,用来保存连接(这个对象不用线程安全,因为初始化就会创建好,其他线程不会更改这个数组,只会更改连接状态数组)
    private Connection[] connections;
    //连接状态数组 0 表示 空闲, 1表示繁忙.和Connection[]对应
    private AtomicIntegerArray states;
    private Semaphore semaphore;

    public PoolV2(int poolSize) {
        this.poolSize = poolSize;
        this.semaphore = new Semaphore(poolSize);
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(poolSize);
        for (int i = 0; i < poolSize; i++) {
            Connection connection = new MockConnection("连接" + (i + 1));
            connections[i] = connection;
            // states.set(i, 0); int 默认就是0
        }
    }

    /**
     * 借连接
     *
     * @return
     */
    public Connection borrow() {
        //直接获取连接,没有连接等待 (semaphore的数量和poolSize大小一样)
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < poolSize; i++) {
            //表示空闲
            if (states.get(i) == 0) {
                //此时需要使用CAS来保证更改成功,成功才能返回
                if (states.compareAndSet(i, 0, 1)) {
                    log.debug("借到连接,{}", connections[i]);
                    return connections[i];
                }
            }
        }
        //不会执行到这.只是为了解决语法错误
        return null;
    }

    /**
     * 归还连接
     *
     * @param connection
     */
    public void free(Connection connection) {
        //检查归还的连接是否是连接池内的
        for (int i = 0; i < poolSize; i++) {
            if (connections[i] == (connection)) {
                //归还连接 (此时不需要CAS,只要它获得了连接,这个连接,就归它使用,归还也没有线程竞争这个值,所以不需要CAS)
                states.set(i, 0);
                //释放
                semaphore.release();
                break;
            }
        }
    }
}

//用来模拟连接
class MockConnection implements Connection {
    private String name;

    public MockConnection(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "MockConnection{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    public Statement createStatement() throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return null;
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return null;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {

    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return false;
    }

    @Override
    public void commit() throws SQLException {

    }

    @Override
    public void rollback() throws SQLException {

    }

    @Override
    public void close() throws SQLException {

    }

    @Override
    public boolean isClosed() throws SQLException {
        return false;
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return null;
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {

    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return false;
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {

    }

    @Override
    public String getCatalog() throws SQLException {
        return null;
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {

    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return 0;
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return null;
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {

    }

    @Override
    public void setHoldability(int holdability) throws SQLException {

    }

    @Override
    public int getHoldability() throws SQLException {
        return 0;
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return null;
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return null;
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {

    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return null;
    }

    @Override
    public Clob createClob() throws SQLException {
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {
        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {
        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return null;
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return false;
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {

    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {

    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return null;
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return null;
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return null;
    }

    @Override
    public void setSchema(String schema) throws SQLException {

    }

    @Override
    public String getSchema() throws SQLException {
        return null;
    }

    @Override
    public void abort(Executor executor) throws SQLException {

    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {

    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return 0;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}
